﻿using System;
using System.Reflection;

using PostSharp.Aspects;
using BizElements.BusinessLayer;

namespace BizElements.BusinessLayer.Aspects
{
    #region Documentation.
    /// <summary>
    /// Marks a method whose execution details are to be logged as transaction.
    /// </summary>
    /// <remarks><para>During the build process the code, which logs operation details, is automatically injected and 
    /// executed after the method marked with <b>LogTransactionAttribute</b> attribute is exited.</para>
    /// <para>To use this attribute you have to install PostSharp 2 on your development computer and add references to <b>PostSharp.dll</b> assembly to your project.</para>
    /// </remarks>
    /// <example>The following example demonstrates how to use the attribute:
    /// <code>
    /// [LogTransaction]
    /// public void Save(IActor actionInitiator)
    /// {
    ///     DAL.GetDao(this).Save();
    /// }
    /// </code>
    /// The previous example is functionally identical to the following pattern:
    /// <code>
    /// public void Save(IActor actionInitiator)
    /// {
    ///     DAL.GetDao(this).Save();
    ///     LogHelper.LogTransaction("Country.Save", /*no description*/ null, actionInitiator, this);
    /// }
    /// </code>
    /// <para><b>RemoteableAttribute</b> and <b>LogTransactionAttribute</b> can be used together. <b>LogTransactionAttribute</b> has lower aspect priority, 
    /// ensuring that the injected code is executed only once, ie. only in domain where method's body is executed.
    /// The following example demonstrates how the attributes are combined:
    /// <code>
    /// [Remoteable, LogTransaction]
    /// public void Save(IActor actionInitiator)
    /// {
    ///     DAL.GetDao(this).Save();
    /// }
    /// </code>
    /// The previous example is functionally identical to the following pattern:
    /// <code>
    /// public void Save(IActor actionInitiator)
    /// {
    ///     RpcHelper.InvokeMethod&lt;IActor&gt;(this, DbSave, actionInitiator);
    /// }
    /// 
    /// protected virtual void DbSave(IActor actionInitiator)
    /// {
    ///     DAL.GetDao(this).Save();
    ///     LogHelper.LogTransaction("Country.Save", /*no description*/ null, actionInitiator, this);
    /// }
    /// </code>
    /// </para>
    /// </example>
    #endregion
    [Serializable]
    [AttributeUsage(AttributeTargets.Method)]
    public sealed class LogTransactionAttribute : OnMethodBoundaryAspect
    {
        #region Constructors.

        /// <summary>
        /// Initializes a new instance of <b>LogTransactionAttribute</b> class and sets its <b>AspectPriority</b>.
        /// </summary>
        public LogTransactionAttribute()
        {
            // Lower priority than Remoteable to ensure that the same transaction is not logged twice.
            // And slightly lower than TransactionRequired so that the following warning is not generated:
            //  Conflicting aspects on "*.Save(BizElements.BusinessLayer.IActor, System.Boolean)": 
            //  transformations "BizElements.BusinessLayer.Aspects.LogTransactionAttribute: 
            //  Wrapped by advice(s) OnExit" and "BizElements.BusinessLayer.Aspects.TransactionRequiredAttribute: Wrapped by advice(s) OnEntry, OnException, OnExit" 
            //  are not commutative, but they are not strongly ordered. Their order of execution is undeterministic.            

            this.AspectPriority = 101;
        }

        /// <summary>
        /// Initializes a new instance of <b>LogTransactionAttribute</b> class and sets its <b>AspectPriority</b>.
        /// </summary>
        /// <param name="description">Short, user-friendly description of the operation/transaction.</param>
        public LogTransactionAttribute(string description)
            : this()
        {
            this.Description = description;
        }

        #endregion

        #region OnMethodBoundaryAspect overrides.

        /// <summary>
        /// Logs low priority, informational message that contains operation/transaction details. Executed <b>after</b> the body of methods to which this aspect is applied. 
        /// </summary>
        /// <param name="eventArgs">Event arguments specifying which method is being executed, which are its arguments, and how should the execution continue after the execution of <b>OnExit</b>.</param>
        public sealed override void OnExit(MethodExecutionArgs eventArgs)
        {
            ILogDetailProvider details = LogAspectConfiguration.DetailProvider;
            Log.Transaction(GetName(eventArgs), GetDescription(eventArgs), details.GetActor(eventArgs), 
                details.GetObjectType(eventArgs), details.GetObjectId(eventArgs), GetObject(eventArgs), details.GetLoggedProperties(eventArgs));
        }

        private string GetName(MethodExecutionArgs eventArgs)
        {
            MethodBase method = eventArgs.Method;
            string tranName = string.IsNullOrEmpty(this.Name) ? method.DeclaringType.Name + "." + method.Name : this.Name;
            return tranName;
        }

        private string GetDescription(MethodExecutionArgs eventArgs)
        {
            return this.Description;
        }

        private object GetObject(MethodExecutionArgs eventArgs)
        {
            return eventArgs.Instance;
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets or sets manually defined transaction name.
        /// </summary>
        /// <value>Transaction name string. Default is <b>null</b> indicating that the name will be automatically created based on the method and class name.</value>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets optional short, user-friendly description of the operation/transaction.
        /// </summary>
        /// <value>A string. Default is <b>null</b>.</value>
        public string Description { get; set; }

        #endregion
    }
}
